package com.sloth.bear.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sloth.bear.config.FileManagerProperties;
import com.sloth.bear.dto.base.ResponsePage;
import com.sloth.bear.dto.entity.FileIndex;
import com.sloth.bear.dto.request.FileQueryRequest;
import com.sloth.bear.dto.request.VideoProgressRequest;
import com.sloth.bear.dto.vo.FileIndexVO;
import com.sloth.bear.mapper.FileIndexMapper;
import com.sloth.bear.service.FileIndexService;
import com.sloth.bear.util.AssertUtils;
import com.sloth.bear.util.BaseHelper;
import com.sloth.bear.util.FileUtils;
import com.sloth.bear.util.ScraperParse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 文件索引服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileIndexServiceImpl extends ServiceImpl<FileIndexMapper, FileIndex> implements FileIndexService {

    private final FileManagerProperties properties;
    private final RedisTemplate<String, Object> redisTemplate;

    private static final String CACHE_KEY_PREFIX = "file_index:";
    private static final String SCAN_STATS_KEY = "file_scan:stats";

    private final AtomicInteger scannedFiles = new AtomicInteger(0);
    private final AtomicInteger addedFiles = new AtomicInteger(0);

    @Override
    public ResponsePage<FileIndexVO> listFile(FileQueryRequest request) {
        if (!properties.getIsEnable()) {
            return ResponsePage.ok();
        }

        Page<FileIndex> page = page(new Page<>(request.getPageNo(), request.getPageSize()), Wrappers.<FileIndex>lambdaQuery()
                .and(StringUtils.isNotBlank(request.getSearch()),
                        i -> i.like(FileIndex::getFileName, request.getSearch())
                                .or().like(FileIndex::getTitle, request.getSearch())
                                .or().like(FileIndex::getPerformer, request.getSearch())
                )
                .orderByDesc(FileIndex::getFileModifiedTime));

        List<FileIndexVO> list = page.getRecords().stream().map(o -> {
            FileIndexVO vo = BaseHelper.r2t(o, FileIndexVO.class);
            vo.setCreatedTime(new Date(o.getFileModifiedTime()));
            return vo;
        }).collect(Collectors.toList());
        return ResponsePage.ok(page, list);
    }

    /**
     * 判断是否为视频文件
     */
    private boolean isVideoFile(String fileType) {
        if (fileType == null) return false;
        String type = fileType.toLowerCase();
        return type.equals("mp4") || type.equals("avi") || type.equals("mkv") ||
                type.equals("mov") || type.equals("wmv") || type.equals("flv") ||
                type.equals("webm") || type.equals("mpg") || type.equals("mpeg") ||
                type.equals("3gp") || type.equals("asf") || type.equals("rm") || type.equals("rmvb");
    }

    @Override
    @Async
    public void scanDirectory(String directory) {
        log.info("开始扫描目录: {}", directory);

        try {
            Path dirPath = Paths.get(directory);
            if (!Files.exists(dirPath) || !Files.isDirectory(dirPath)) {
                log.warn("目录不存在或不是有效目录: {}", directory);
                return;
            }

            scannedFiles.set(0);
            addedFiles.set(0);

            Files.walk(dirPath)
                    .filter(Files::isRegularFile)
                    .forEach(this::processFile);

            // 更新扫描统计
            updateScanStatistics(directory);

            log.info("目录扫描完成: {}, 扫描文件数: {}, 添加到索引: {}",
                    directory, scannedFiles.get(), addedFiles.get());

        } catch (Exception e) {
            log.error("扫描目录失败: {}", directory, e);
        }
    }

    @Override
    public void scanAllDirectories() {
        List<String> directories = properties.getScan().getDirectories();
        if (directories == null || directories.isEmpty()) {
            log.warn("未配置扫描目录");
            return;
        }

        for (String directory : directories) {
            scanDirectory(directory);
        }
    }

    @Override
    public void addFileToIndex(String filePath) {
        try {
            Path path = Paths.get(filePath);
            if (Files.exists(path)) {
                processFile(path);
            }
        } catch (Exception e) {
            log.error("添加文件到索引失败: {}", filePath, e);
        }
    }

    @Override
    public void removeFileFromIndex(String filePath) {
        try {
            LambdaQueryWrapper<FileIndex> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(FileIndex::getFilePath, filePath);

            FileIndex existingFile = getOne(wrapper);
            if (existingFile != null) {
                removeById(existingFile.getId());
                log.info("从索引中移除文件: {}", filePath);
            }
        } catch (Exception e) {
            log.error("从索引中移除文件失败: {}", filePath, e);
        }
    }

    @Override
    public void refreshCache() {
        // 清除相关缓存
        String pattern = CACHE_KEY_PREFIX + "*";
        redisTemplate.delete(redisTemplate.keys(pattern));
        log.info("缓存已刷新");
    }

    @Override
    public String getScanStatistics() {
        Object stats = redisTemplate.opsForValue().get(SCAN_STATS_KEY);
        return stats != null ? stats.toString() : "暂无扫描统计信息";
    }

    @Override
    public FileIndexVO getFileDetail(Long id) {
        if (!properties.getIsEnable()) {
            return null;
        }

        FileIndex fileIndex = getById(id);
        AssertUtils.notNull(fileIndex, "文件不存在");

        FileIndexVO vo = BaseHelper.r2t(fileIndex, FileIndexVO.class);
        vo.setCreatedTime(new Date(fileIndex.getFileModifiedTime()));
        return vo;
    }

    @Override
    public FileIndexVO getRandomVideo() {
        // 查询所有视频文件，随机选择一个
        LambdaQueryWrapper<FileIndex> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(FileIndex::getFileType, "mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "mpg", "mpeg", "3gp", "asf", "rm", "rmvb");
        wrapper.last("ORDER BY RAND() LIMIT 1"); // MySQL随机排序

        FileIndex fileIndex = getOne(wrapper);
        if (fileIndex == null) {
            throw new RuntimeException("没有找到可播放的视频文件");
        }

        FileIndexVO vo = BaseHelper.r2t(fileIndex, FileIndexVO.class);
        vo.setCreatedTime(new Date(fileIndex.getFileModifiedTime()));
        return vo;
    }

    @Override
    public void saveVideoProgress(VideoProgressRequest request) {
        update(Wrappers.<FileIndex>lambdaUpdate()
                .eq(FileIndex::getId, request.getFileId())
                .set(FileIndex::getCurrentTime, request.getCurrentTime())
                .set(FileIndex::getDuration, request.getDuration())
                .set(FileIndex::getProgressPercent, request.getDuration() > 0 ? (request.getCurrentTime() / request.getDuration() * 100) : 0)
        );
    }

    /**
     * 处理单个文件
     */
    private void processFile(Path filePath) {
        try {
            File file = filePath.toFile();
            scannedFiles.incrementAndGet();

            // 检查文件是否符合条件
            if (!FileUtils.isValidFile(file.getName(), file.length(),
                    properties.getScan().getFileSizeThreshold().toBytes(),
                    properties.getScan().getSupportedFormats())) {
                return;
            }

            String absolutePath = file.getAbsolutePath();

            // 先查询包括已删除的记录，避免唯一键冲突
            FileIndex existingFile = baseMapper.selectByFilePathIncludeDeleted(absolutePath);

            if (existingFile != null) {
                // 如果记录被逻辑删除，恢复它
                if (existingFile.getDeleted() != null && existingFile.getDeleted() == 1) {
                    existingFile.setDeleted(0);
                    existingFile.setFileSize(file.length());
                    existingFile.setFileModifiedTime(file.lastModified());
                    existingFile.setFileSizeReadable(FileUtils.formatFileSize(file.length()));
                    updateById(existingFile);
                    addedFiles.incrementAndGet();
                    log.debug("恢复已删除的文件索引: {}", absolutePath);
                } else {
                    // 检查文件是否有更新
                    if (existingFile.getFileModifiedTime() != file.lastModified()) {
                        updateFileIndex(existingFile, file);
                    }
                }
                return;
            }

            // 创建新的文件索引
            FileIndex fileIndex = createFileIndex(file);
            save(fileIndex);
            addedFiles.incrementAndGet();

            log.debug("添加文件到索引: {}", absolutePath);

        } catch (Exception e) {
            log.error("处理文件失败: {}", filePath, e);
        }
    }

    /**
     * 创建文件索引对象
     */
    private FileIndex createFileIndex(File file) {
        FileIndex fileIndex = new FileIndex();
        fileIndex.setFilePath(file.getAbsolutePath());
        fileIndex.setFileName(file.getName());
        fileIndex.setFileSize(file.length());
        fileIndex.setFileType(FileUtils.getFileExtension(file.getName()));
        fileIndex.setFileModifiedTime(file.lastModified());
        fileIndex.setDirectory(FileUtils.getDirectory(file.getAbsolutePath()));
        fileIndex.setFileSizeReadable(FileUtils.formatFileSize(file.length()));
        return fileIndex;
    }

    /**
     * 更新文件索引
     */
    private void updateFileIndex(FileIndex existingFile, File file) {
        existingFile.setFileSize(file.length());
        existingFile.setFileModifiedTime(file.lastModified());
        existingFile.setFileSizeReadable(FileUtils.formatFileSize(file.length()));
        updateById(existingFile);
        log.debug("更新文件索引: {}", file.getAbsolutePath());
    }

    /**
     * 更新扫描统计信息
     */
    private void updateScanStatistics(String directory) {
        String stats = String.format("最近扫描: %s, 目录: %s, 扫描文件数: %d, 新增索引: %d",
                LocalDateTime.now(), directory, scannedFiles.get(), addedFiles.get());

        redisTemplate.opsForValue().set(SCAN_STATS_KEY, stats,
                properties.getCache().getExpiration(), TimeUnit.SECONDS);
    }

    @Override
    @Async
    public void scrapeAllVideos() {
        log.info("开始刮削所有视频文件");

        try {
            // 查询所有视频文件
            LambdaQueryWrapper<FileIndex> wrapper = new LambdaQueryWrapper<>();
            wrapper.in(FileIndex::getFileType, "mp4", "avi", "mkv", "mov", "wmv", "flv", "webm", "mpg", "mpeg", "3gp", "asf", "rm", "rmvb");

            // 只刮削还没有元数据的视频（没有title的）
            wrapper.and(w -> w.isNull(FileIndex::getTitle).or().eq(FileIndex::getTitle, ""));

            List<FileIndex> videoFiles = list(wrapper);

            if (videoFiles.isEmpty()) {
                log.info("没有需要刮削的视频文件");
                return;
            }

            log.info("找到 {} 个需要刮削的视频文件", videoFiles.size());

            int successCount = 0;
            int failCount = 0;

            for (FileIndex fileIndex : videoFiles) {
                try {
                    // 调用刮削工具
                    ScraperParse.parse(fileIndex.getFileName(), fileIndex);

                    // 更新数据库
                    updateById(fileIndex);
                    successCount++;

                    // 避免请求过快，休眠一下
                    Thread.sleep(1000);
                } catch (Exception e) {
                    log.error("刮削文件失败: {}", fileIndex.getFileName(), e);
                    failCount++;
                }
            }

            log.info("刮削完成，成功: {}, 失败: {}", successCount, failCount);
        } catch (Exception e) {
            log.error("刮削任务执行失败", e);
        }
    }

    @Override
    @Async
    public void restoreDeletedFiles() {
        log.info("开始恢复已删除的文件记录");

        try {
            // 查询所有已删除的记录（绕过逻辑删除）
            List<FileIndex> deletedFiles = baseMapper.selectList(
                    new LambdaQueryWrapper<FileIndex>()
                            .eq(FileIndex::getDeleted, 1)
            );

            if (deletedFiles.isEmpty()) {
                log.info("没有已删除的文件记录");
                return;
            }

            log.info("找到 {} 个已删除的文件记录，开始检查文件是否存在", deletedFiles.size());

            int restoredCount = 0;

            for (FileIndex fileIndex : deletedFiles) {
                try {
                    Path filePath = Paths.get(fileIndex.getFilePath());

                    // 检查文件是否存在
                    if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
                        // 文件存在，恢复记录
                        fileIndex.setDeleted(0);
                        fileIndex.setFileSize(Files.size(filePath));
                        fileIndex.setFileModifiedTime(Files.getLastModifiedTime(filePath).toMillis());
                        fileIndex.setFileSizeReadable(FileUtils.formatFileSize(Files.size(filePath)));
                        updateById(fileIndex);
                        restoredCount++;
                        log.info("恢复文件记录: {}", fileIndex.getFilePath());
                    }
                } catch (Exception e) {
                    log.warn("检查文件失败: {}", fileIndex.getFilePath(), e);
                }
            }

            log.info("恢复完成，成功恢复: {} 个文件", restoredCount);
        } catch (Exception e) {
            log.error("恢复文件记录任务执行失败", e);
        }
    }

    @Override
    public void cleanDeletedFiles() {
        log.info("开始清理已删除的文件记录");

        try {
            // 物理删除所有 deleted=1 的记录
            int count = baseMapper.delete(
                    new LambdaQueryWrapper<FileIndex>()
                            .eq(FileIndex::getDeleted, 1)
            );

            log.info("清理完成，删除了 {} 条记录", count);
        } catch (Exception e) {
            log.error("清理已删除文件记录失败", e);
            throw new RuntimeException("清理失败: " + e.getMessage());
        }
    }

    @Override
    @Async
    public void forceFullRefresh() {
        log.info("开始强制全量刷新，按文件修改时间从新到旧扫描");

        List<String> directories = properties.getScan().getDirectories();
        if (directories == null || directories.isEmpty()) {
            log.warn("未配置扫描目录");
            return;
        }

        try {
            scannedFiles.set(0);
            addedFiles.set(0);

            for (String directory : directories) {
                log.info("扫描目录: {}", directory);

                Path dirPath = Paths.get(directory);
                if (!Files.exists(dirPath) || !Files.isDirectory(dirPath)) {
                    log.warn("目录不存在或不是有效目录: {}", directory);
                    continue;
                }

                // 收集所有文件并按修改时间排序（从新到旧）
                List<Path> allFiles = Files.walk(dirPath)
                        .filter(Files::isRegularFile)
                        .sorted((p1, p2) -> {
                            try {
                                long t1 = Files.getLastModifiedTime(p1).toMillis();
                                long t2 = Files.getLastModifiedTime(p2).toMillis();
                                return Long.compare(t2, t1); // 降序：新文件在前
                            } catch (Exception e) {
                                return 0;
                            }
                        })
                        .collect(Collectors.toList());

                log.info("目录 {} 下找到 {} 个文件，开始处理", directory, allFiles.size());

                // 处理每个文件
                for (Path filePath : allFiles) {
                    processFileForceRefresh(filePath);
                }

                // 更新扫描统计
                updateScanStatistics(directory);
            }

            log.info("强制全量刷新完成，扫描文件数: {}, 新增/更新索引: {}",
                    scannedFiles.get(), addedFiles.get());

        } catch (Exception e) {
            log.error("强制全量刷新失败", e);
        }
    }

    /**
     * 处理单个文件（强制刷新模式）
     * 强制更新或创建记录，不管文件是否有变化
     */
    private void processFileForceRefresh(Path filePath) {
        try {
            File file = filePath.toFile();
            scannedFiles.incrementAndGet();

            // 检查文件是否符合条件
            if (!FileUtils.isValidFile(file.getName(), file.length(),
                    properties.getScan().getFileSizeThreshold().toBytes(),
                    properties.getScan().getSupportedFormats())) {
                return;
            }

            String absolutePath = file.getAbsolutePath();

            // 查询记录（包括已删除的）
            FileIndex existingFile = baseMapper.selectByFilePathIncludeDeleted(absolutePath);

            if (existingFile != null) {
                // 如果记录被逻辑删除，恢复它
                if (existingFile.getDeleted() != null && existingFile.getDeleted() == 1) {
                    existingFile.setDeleted(0);
                }
                // 强制更新文件信息（不管是否有变化）
                existingFile.setFileSize(file.length());
                existingFile.setFileModifiedTime(file.lastModified());
                existingFile.setFileSizeReadable(FileUtils.formatFileSize(file.length()));
                updateById(existingFile);
                addedFiles.incrementAndGet();
                log.debug("更新文件索引: {}", absolutePath);
            } else {
                // 创建新的文件索引
                FileIndex fileIndex = createFileIndex(file);
                save(fileIndex);
                addedFiles.incrementAndGet();
                log.debug("添加文件到索引: {}", absolutePath);
            }

        } catch (Exception e) {
            log.error("处理文件失败: {}", filePath, e);
        }
    }
}